#include <usb-device-manager.h>
#include <spice-gtk-session.h>
#include <usb-device-widget.h>
#include <glib-object.h>
#include <json-glib/json-glib.h>
#include "vcd-common.h"
#include "vcd-usb-redir.h"
#include "vcd-conf/vcd-conf.h"

#ifdef USE_USBREDIR

#include <libusb.h>

VcdUsbRedir *g_vcd_usb_redir = NULL;

static void close_redire_win(GtkWidget* btn, gpointer pt);
static void usb_connect_failed(GObject               *object,
                               SpiceUsbDevice        *device,
                               GError                *error,
                               gpointer               data);
static void remove_cb(GtkContainer *container, GtkWidget *widget, void *data);
static void usb_select_callback_internal(gconstpointer pt, gboolean bselect,gpointer user_data);

static int generate_and_save_usb_redir_state(UsbRedirState *rs);
gchar* get_usb_redir_config_path(void);
JsonArray* generate_usb_redir_arr(GPtrArray *arr);
static int parse_usb_redir_state(UsbRedirState *rs);
GPtrArray*  parse_usb_redir_arr(JsonArray * arr);
static UsbRedirState* create_usb_redir_state(void);
static gboolean usb_can_auto_redir_callback_internal(gconstpointer pt, gpointer user_data);
JsonArray* generate_usb_redir_arr_differ(GPtrArray *arr);
GPtrArray*  parse_usb_redir_arr_differ(JsonArray * arrj);
static void set_select_state(GPtrArray *arr, gint64 vendor_id, gint64 product_id, gboolean bselect);
static void delete_not_exit_usb_device(VcdUsbRedir *rdr, UsbRedirState *rs);
static void delete_not_exit_usb_device_simple(VcdUsbRedir *rdr, UsbRedirState *rs, GPtrArray *arr);

UsbRedirState *g_usb_redir_state = NULL;

//static void show_redir_info(VcdUsbRedir *rdr);

static void usb_connect_failed(GObject               *object,
                               SpiceUsbDevice        *device,
                               GError                *error,
                               gpointer               data)
{
    GtkWidget *dialog;

    if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
        return;

    dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
                                    GTK_BUTTONS_CLOSE,
                                    "USB redirection error");
    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
                                             "%s", error->message);
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
}

static void remove_cb(GtkContainer *container, GtkWidget *widget, void *data)
{
    VcdUsbRedir *self = (VcdUsbRedir*)data;
    if (self->redir_win)
        gtk_window_resize(GTK_WINDOW(self->redir_win), 1, 1);

    delete_not_exit_usb_device(self, g_usb_redir_state);
}

static void close_redire_win(GtkWidget* btn, gpointer pt)
{
    VcdUsbRedir *self = (VcdUsbRedir*)pt;
    if (self->redir_win) {
        gtk_widget_destroy(self->redir_win);
        self->redir_win = NULL;
    }
}

static gboolean usb_device_exist(VcdUsbRedir *rdr, UsbOneState *state)
{
    GError *err = NULL;
    GPtrArray *devices = NULL;
    SpiceUsbDeviceManager *mgr =spice_usb_device_manager_get(rdr->session, &err);
    devices = spice_usb_device_manager_get_devices(mgr);
    if (devices == NULL) {
        g_error_free(err);
        return FALSE;
    }

    int i;
    for (i = 0; i < devices->len; i++) {
        SpiceUsbDevice *device = g_ptr_array_index(devices, i);
        int errcode = 0;
        struct libusb_device_descriptor desc;
        const libusb_device *libdev = spice_usb_device_get_libusb_device(device);
        errcode = libusb_get_device_descriptor((libusb_device*)libdev, &desc);
        if (errcode < 0)
            continue;

        if (desc.idVendor == state->vendor_id && desc.idProduct == state->product_id) {
            g_ptr_array_unref(devices);
            return TRUE;
        }
    }

    g_ptr_array_unref(devices);
    return FALSE;
}

static void delete_not_exit_usb_device_simple(VcdUsbRedir *rdr, UsbRedirState *rs, GPtrArray *arr)
{
    g_return_if_fail(rs != NULL);

    guint i = 0;
    for (i = 0; i < arr->len; ++i) {
        UsbOneState *p = (UsbOneState*)g_ptr_array_index(arr, i);
        gboolean bres = usb_device_exist(rdr, p);
        if (!bres) {
            UsbOneState *rm = g_ptr_array_remove_index(arr, i);
            G_SAFE_FREE(rm);
        }
    }
}

static void delete_not_exit_usb_device(VcdUsbRedir *rdr, UsbRedirState *rs)
{
    g_return_if_fail(rs != NULL);
    g_return_if_fail(rdr != NULL);

    delete_not_exit_usb_device_simple(rdr, rs, rs->arr_apply_all);

    GPtrArray *arr = rs->arr_apply_differ;
    if (arr == NULL)
        return;

    guint i = 0;
    for (i = 0; i < arr->len; ++i) {
        UsbRedirOneVm *p = (UsbRedirOneVm*)g_ptr_array_index(arr, i);
        delete_not_exit_usb_device_simple(rdr, rs, p->arr);
    }

    //save data to file
    generate_and_save_usb_redir_state(rs);
}

//检测是否能自动连接usb,表示版本和出厂号码匹配
static gboolean get_usb_redir_state(GPtrArray *arr, gint64 vendor_id, gint64 product_id)
{
    if (!arr) 
        return FALSE;

    gint64 i = 0;
    for (i = 0; i < (gint64)arr->len; ++i) {
        UsbOneState* state = (UsbOneState*)g_ptr_array_index(arr,i);
        if (state->vendor_id == vendor_id &&
                state->product_id == product_id) {
            return state->redir_state;
        }
    }
    return FALSE;
}

static gboolean usb_can_auto_redir_callback_internal(gconstpointer pt, gpointer user_data)
{
    struct libusb_device_descriptor desc;
    gint err = libusb_get_device_descriptor((libusb_device*)pt, &desc);
    if (err < 0)
        return FALSE;

    gint64 vid = desc.idVendor;
    gint64 pid = desc.idProduct;

    UsbRedirState* rs = (UsbRedirState*)user_data;
    g_return_val_if_fail(rs != NULL, FALSE);
    gint64 i = 0;
    if (rs->flag) {
        //这里是检测是否能自动连接usb，第一次rs->arr_apply_all=NULL，应该返回false
        return get_usb_redir_state(rs->arr_apply_all, vid, pid);
    } else {
        //这里除了检测是否能自动连usb还要匹配与记录的ip端口是否匹配
        GPtrArray *arr = rs->arr_apply_differ;
        g_return_val_if_fail(arr != NULL, FALSE);
        for (i = 0; i < (gint64)arr->len; ++i) {
            UsbRedirOneVm *p = (UsbRedirOneVm*)g_ptr_array_index(arr, i);
            if (strcmp(p->ip, rs->cur_ip) == 0 &&
                    p->port == rs->cur_port) {
                return get_usb_redir_state(p->arr, vid, pid);
            }
        }
    }
    return FALSE;
}

static void set_select_state(GPtrArray *arr, gint64 vendor_id, gint64 product_id, gboolean bselect)
{
    M_DEBUG("USB--set_select_state");
    g_return_if_fail(arr != NULL);

    gint64 i = 0;
    for (i = 0; i < (gint64)arr->len; ++i) {
        UsbOneState* state = (UsbOneState*)g_ptr_array_index(arr,i);
        if (state->vendor_id == vendor_id &&
                state->product_id == product_id) {
            state->redir_state = bselect;
            break;
        }
    }
    //not found then add
    if (i == (gint64)arr->len) {
        UsbOneState *state = g_malloc0(sizeof(UsbOneState));
        g_return_if_fail(state != NULL);
        state->vendor_id = vendor_id;
        state->product_id = product_id;
        state->redir_state= bselect;
        g_ptr_array_add(arr, state);
    }
}

//如果说第一次插入的u盘无任何之前记录的信息的，是可以直接连进来的，这边是连接usb之后的回调，会保存连接的记录
static void usb_select_callback_internal(gconstpointer pt, gboolean bselect, gpointer user_data)
{
    M_DEBUG("USB--usb_select_callback_internal, some usb is connected");
    struct libusb_device_descriptor desc;
    gint err = libusb_get_device_descriptor((libusb_device*)pt, &desc);
    if (err < 0)
        return;

    gint64 vid = desc.idVendor;
    gint64 pid = desc.idProduct;
    M_DEBUG("USB--desc.idVendor=%d, desc.idProduct=%d", desc.idVendor, desc.idProduct);

    UsbRedirState* rs = (UsbRedirState*)user_data;//这是初始化时传入的usb数据结构UsbRedirState
    g_return_if_fail(rs != NULL);
    gint64 i = 0;
    if (rs->flag) {
        //apply all = 1不需要校对ip和端口
        set_select_state(rs->arr_apply_all, vid, pid, bselect);
    } else {
        //apply all = 0需要校对ip和端口
        GPtrArray* arr = rs->arr_apply_differ;
        for (i = 0; i < (gint64)arr->len; ++i) {
            UsbRedirOneVm* state = (UsbRedirOneVm*)g_ptr_array_index(arr,i);
            if(strcmp(state->ip, rs->cur_ip) == 0 &&
                    state->port == rs->cur_port) {
                set_select_state(state->arr, vid, pid, bselect);
                break;
            }
        }

        //add if not found
        if (i == (gint64)arr->len) {
            UsbRedirOneVm *p = (UsbRedirOneVm*)g_malloc0(sizeof(UsbRedirOneVm));
            g_return_if_fail(p != NULL);
            strncpy(p->ip, rs->cur_ip, sizeof(p->ip));
            p->port = rs->cur_port;
            p->arr = g_ptr_array_new();
            g_return_if_fail(p->arr != NULL);
            set_select_state(p->arr, vid, pid, bselect);
            g_ptr_array_add(arr, p);
        }
    }

    //save data to file
    generate_and_save_usb_redir_state(rs);
}

gchar* get_usb_redir_config_path(void)
{
    const gchar* dir = g_get_user_config_dir();
    char path[255];
    snprintf(path, sizeof(path), "%s/spicy/usb_redir.json", dir);
    return g_strdup(path);
}

/*
static void show_redir_info(VcdUsbRedir *rdr)
{
    GError *err = NULL;
    GPtrArray *devices = NULL;
    SpiceUsbDeviceManager *mgr =spice_usb_device_manager_get(rdr->session, &err);
    devices = spice_usb_device_manager_get_devices(mgr);
    if(devices == NULL){
        return;
    }

    int i;
    for (i = 0; i < devices->len; i++) {
        SpiceUsbDevice *device = g_ptr_array_index(devices, i);
        int errcode;
        struct libusb_device_descriptor desc;
        const libusb_device *libdev = spice_usb_device_get_libusb_device(device);
        errcode = libusb_get_device_descriptor(libdev, &desc);
            if (errcode < 0) {
                continue;
            }

            if(desc.idVendor == 0x0951 && desc.idProduct == 0x1666){
          //      spice_usb_device_manager_connect_device_async(mgr, device, NULL, NULL, NULL);
            }


        M_DEBUG("vender:0x%04x, pid:0x%04x", desc.idVendor, desc.idProduct);
    }

    g_ptr_array_unref(devices);

}*/

/* 创建usb选项的窗口，在点击悬浮框的时候出来 */
gboolean vcd_usb_redir(GtkWidget* btn, gpointer pt)
{
    GtkWidget *redir_win = NULL;
    GtkWidget *box = NULL;
    GtkWidget *fix = NULL;
    GtkWidget *btn_close = NULL;
    GtkWidget *usb_device_widget;
    gint width, height;

    VcdUsbRedir *self = (VcdUsbRedir*)pt;

    g_return_val_if_fail(pt != NULL, FALSE);

    if (self->redir_win) {
        gtk_widget_destroy(self->redir_win);
        self->redir_win = NULL;
    }

    //delete_not_exit_usb_device(self, g_usb_redir_state);

    redir_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(redir_win), GTK_WIN_POS_CENTER_ALWAYS);//居中
    gtk_window_set_decorated(GTK_WINDOW(redir_win), FALSE);//没有菜单栏
    gtk_container_set_border_width(GTK_CONTAINER(redir_win), 25);
    gtk_window_set_keep_above(GTK_WINDOW(redir_win), TRUE);
    gtk_window_set_modal((GtkWindow *)redir_win, TRUE);
    if(g_vcd_usb_redir->mother_window)
        gtk_window_set_transient_for(GTK_WINDOW(redir_win), GTK_WINDOW(g_vcd_usb_redir->mother_window));
    self->redir_win = redir_win;

    btn_close = gtk_button_new_with_label("关闭");
    gtk_widget_set_size_request(btn_close, 60, 35);

    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);

    usb_device_widget = spice_usb_device_widget_new(self->session,
                                                    NULL); /* default format */

    g_signal_connect(usb_device_widget, "connect-failed",
                     G_CALLBACK(usb_connect_failed), NULL);
    gtk_box_pack_start(GTK_BOX(box), usb_device_widget, TRUE, TRUE, 15);
    gtk_box_set_spacing(GTK_BOX(usb_device_widget), 15);

    gtk_window_get_size(GTK_WINDOW(redir_win), &width, &height);

    fix = gtk_fixed_new();
    gtk_fixed_put(GTK_FIXED(fix), btn_close, width - 5, 0);
    gtk_box_pack_start(GTK_BOX(box), fix, TRUE, TRUE, 0);
    g_signal_connect(G_OBJECT(btn_close), "clicked", G_CALLBACK(close_redire_win), self);

    /* This shrinks the dialog when USB devices are unplugged */
    g_signal_connect(usb_device_widget, "remove",
                     G_CALLBACK(remove_cb), self);
    gtk_container_add(GTK_CONTAINER(redir_win), box);

    /* show and run */
    gtk_widget_show_all(redir_win);

    return TRUE;
}


JsonArray* generate_usb_redir_arr(GPtrArray *arr)
{
    guint i = 0;
    JsonArray *arrj = json_array_new();
    g_return_val_if_fail(arrj != NULL, NULL);

    for (i = 0; i < arr->len; ++i) {
        JsonObject *obj = json_object_new();
        UsbOneState *state = (UsbOneState*)g_ptr_array_index(arr,i);
        if (state == NULL)
            continue;
        json_object_set_int_member(obj,"vendor_id", state->vendor_id);
        json_object_set_int_member(obj,"product_id", state->product_id);
        json_object_set_int_member(obj,"redir_state", state->redir_state);
        json_array_add_object_element(arrj, obj);
    }
    return arrj;
}

JsonArray* generate_usb_redir_arr_differ(GPtrArray *arr)
{
    guint i = 0;
    JsonArray *arrj = json_array_new();
    g_return_val_if_fail(arrj != NULL, NULL);

    for (i = 0; i < arr->len; ++i) {
        JsonObject *obj = json_object_new();
        UsbRedirOneVm *state = (UsbRedirOneVm*)g_ptr_array_index(arr,i);
        if (state == NULL)
            continue;
        json_object_set_string_member(obj,"ip", state->ip);
        json_object_set_int_member(obj,"port", state->port);

        JsonArray *ay = generate_usb_redir_arr(state->arr);
        json_object_set_array_member(obj,"state", ay);

        json_array_add_object_element(arrj, obj);
    }
    return arrj;
}


static int generate_and_save_usb_redir_state(UsbRedirState *rs)
{
    JsonNode *node = NULL;
    JsonGenerator *generator = NULL;
    JsonObject *obj = json_object_new();
    g_return_val_if_fail(obj != NULL, -1);

    json_object_set_int_member(obj, "apply_all", rs->flag);

    if (rs->flag) {
        JsonArray *arr = generate_usb_redir_arr(rs->arr_apply_all);
        if (arr)
            json_object_set_array_member(obj,"data", arr);
    } else {
        JsonArray *arr = generate_usb_redir_arr_differ(rs->arr_apply_differ);
        if (arr)
            json_object_set_array_member(obj,"data", arr);
    }

    generator = json_generator_new();
    json_generator_set_pretty(generator, TRUE);

    node = json_node_init_object(json_node_alloc(), obj);
    json_generator_set_root (generator, node);

    gchar *path = get_usb_redir_config_path();
    if (path == NULL) {
        g_free(generator);
        return -1;
    }

    GError *err=NULL;
    json_generator_to_file(generator, path, &err);
    if (err)
        g_error_free(err);

    //g_free(generator);
    return 0;
}

GPtrArray*  parse_usb_redir_arr(JsonArray * arrj)
{
    GPtrArray *ar = g_ptr_array_new();
    g_return_val_if_fail(ar != NULL, NULL);
    guint num = 0;
    guint i = 0;
    num = json_array_get_length(arrj);

    for (i = 0; i< num; ++i) {
        UsbOneState *state = (UsbOneState*)g_malloc0(sizeof(UsbOneState));
        g_return_val_if_fail(state != NULL, NULL);

        JsonObject *j = json_array_get_object_element(arrj, i);
        if (json_object_has_member(j, "vendor_id"))
            state->vendor_id = json_object_get_int_member(j, "vendor_id");
        
        if (json_object_has_member(j, "product_id"))
            state->product_id = json_object_get_int_member(j, "product_id");

        if (json_object_has_member(j, "redir_state"))
            state->redir_state = json_object_get_int_member(j, "redir_state");
        
        M_DEBUG("USB--index=%d, vendor_id=%d, product_id=%d, redir_state=%d", i, state->vendor_id, state->product_id, state->redir_state);
        g_ptr_array_add(ar, state);
    }
    return ar;
}

GPtrArray* parse_usb_redir_arr_differ(JsonArray *arrj)
{
    GPtrArray *ar = g_ptr_array_new();
    g_return_val_if_fail(ar != NULL, NULL);
    guint num = 0;
    guint i = 0;

    num = json_array_get_length(arrj);

    for (i = 0; i< num; ++i) {
        UsbRedirOneVm *p = (UsbRedirOneVm*)g_malloc0(sizeof(UsbRedirOneVm));
        g_return_val_if_fail(p != NULL, NULL);

        JsonObject *j = json_array_get_object_element(arrj, i);
        if (json_object_has_member(j, "ip")) {
            snprintf(p->ip, sizeof(p->ip), "%s", json_object_get_string_member(j, "ip"));
        }
        if (json_object_has_member(j, "port")) {
            p->port = json_object_get_int_member(j, "port");
        }

        if (json_object_has_member(j, "state")) {
            JsonArray *ay = json_object_get_array_member(j, "state");
            GPtrArray * g = parse_usb_redir_arr(ay);
            p->arr = g;
        }
        g_ptr_array_add(ar, p);
    }

    return ar;
}

/* 读取并解析配置文件 */
static int parse_usb_redir_state(UsbRedirState *rs)
{
    gboolean bres = FALSE;
    gboolean apply_all = TRUE;
    JsonParser *parser = NULL;
    GError *err = NULL;
    JsonNode *node1 = NULL;
    JsonObject *obj1 = NULL;
    gchar *file_path = NULL;
    JsonArray * arrj = NULL;

    g_return_val_if_fail(rs != NULL, -1);

    parser = json_parser_new();
    g_return_val_if_fail(parser != NULL, -1);

    file_path = get_usb_redir_config_path();
    if (!file_path)
        goto fail;
    M_DEBUG("system config path: %s", file_path);

    bres = json_parser_load_from_file(parser, file_path, &err);
    if (bres != TRUE)
        goto fail;

    node1 = json_parser_get_root(parser);
    if (!node1)
        goto fail;
    
    obj1 = json_node_get_object(node1);
    if (!obj1)
        goto fail;

    if (json_object_has_member(obj1, "apply_all"))
        apply_all = json_object_get_int_member(obj1, "apply_all");
    
    if (!json_object_has_member(obj1, "data"))
        goto fail;
    
    arrj = json_object_get_array_member(obj1, "data");

    if (apply_all) {
        M_DEBUG("USB--apply_all = TRUE");
        GPtrArray *arr = parse_usb_redir_arr(arrj);
        if (!arr)
            goto fail;
        rs->arr_apply_all = arr;
    } else {
        M_DEBUG("USB--apply_all = FALSE");
        GPtrArray *arr = parse_usb_redir_arr_differ(arrj);
        if (!arr)
            goto fail;
        rs->arr_apply_differ = arr;
    }

    g_object_unref(parser);
    G_SAFE_FREE(file_path);
    return 0;
fail:
    g_object_unref(parser);
    if (file_path)
        g_free(file_path);
    
    if (err)
        g_error_free(err);
    
    return -1;
}

static UsbRedirState* create_usb_redir_state(void)
{   
    GPtrArray *ptr_arr = NULL, *ptr_arr2 = NULL;
    UsbRedirState* rs = (UsbRedirState*)g_malloc0(sizeof(UsbRedirState));
    g_return_val_if_fail(rs != NULL, NULL);
    ptr_arr = g_ptr_array_new();
    if (!ptr_arr)
        goto fail;

    //此处要使用必须要read_vcd_conf,但是windows暂时不读取配置文件
    if (g_vcd_conf)
        rs->flag = g_vcd_conf->usb_redir_apply_all_flag;

    rs->arr_apply_all = ptr_arr;

    ptr_arr2 = g_ptr_array_new();
    if (!ptr_arr2)
        goto fail;

    rs->arr_apply_differ = ptr_arr2;

    return rs;
fail:
    G_SAFE_FREE(rs);

    if (ptr_arr)
        g_ptr_array_unref(ptr_arr);
    
    return NULL;
}

VcdUsbRedir* create_vcd_usb_redir(SpiceSession *session, GtkWidget *mother_window)
{
    VcdUsbRedir *r = g_vcd_usb_redir;
    if (!r) {
        r = (VcdUsbRedir*)g_malloc0(sizeof(VcdUsbRedir));
        g_vcd_usb_redir = r;
    }
    g_return_val_if_fail(r != NULL, NULL);
    r->session = session;
    r->mother_window = mother_window;

    return r;
}

int destroy_vcd_usb_redir(void)
{
    VcdUsbRedir *r = g_vcd_usb_redir;
    if (!r) 
        return 0;

    if (r->redir_win) {
        gtk_widget_destroy(r->redir_win);
        r->redir_win = NULL;
    }

    G_SAFE_FREE(r);

    g_vcd_usb_redir = NULL;
    return 0;
}

/* 读取配置文件，在连接虚拟机的时候被调用 */
int init_usb_redir_operate_callback(SpiceSession *session, const gchar* ip, const char* port)
{
    SpiceUsbDeviceManager *mgr = NULL;
    GError *err = NULL;

    if (g_usb_redir_state == NULL) {
        UsbRedirState * rs = create_usb_redir_state();
        parse_usb_redir_state(rs);//解析配置文件中的设备，第一次应该解析不到
        g_usb_redir_state = rs;
    }    

    snprintf(g_usb_redir_state->cur_ip, sizeof(g_usb_redir_state->cur_ip), "%s", ip);
    g_usb_redir_state->cur_port = atoi(port);

    mgr = spice_usb_device_manager_get(session, &err);
    if (err) {
        g_error_free(err);
        err = NULL;
        destroy_vcd_usb_redir();
        return -1;
    }
    spice_usb_device_manager_set_select_callback(mgr,
                                                 (gpointer)usb_select_callback_internal,
                                                 (gpointer)usb_can_auto_redir_callback_internal,
                                                 (gpointer)g_usb_redir_state);
    return 0;
}

#endif


